use bevy::prelude::*;
use std::{
    f32::consts::PI,
    fmt::Display,
    io::prelude::*,
    process::{Child, Command, Stdio},
    time::{Duration, SystemTime},
};

// 相机
pub const CAMERA_HEIGHT: f32 = 1.5;
pub const CAMERA_HORIZONTAL_DISTANCE: f32 = 4.0;
// 机器人
pub const ROBOT_RADIUS: f32 = 0.3;
pub const ROBOT_HEIGHT: f32 = 0.3;
pub const ROBOT_ROTATE_SPEED: f32 = (2.0 * PI) / 3.9;
pub const ROBOT_MOVE_SPEED: f32 = 4.0;
pub const CAUGHT_DISTANCE: f32 = 0.3;
// 球
pub const BALL_RADIUS: f32 = 0.15;
pub const BALL_KICK_SPEED: f32 = 12.0;
pub const ROBOT_SHOT_RELAXING_TIME: f32 = 1.0;
// 出球速度
pub const BALL_MOVE_SPEED_MIN: f32 = 3.5;
pub const BALL_MOVE_SPEED_MAX: f32 = 5.0;
pub const BALL_MOVE_ANGLE_MIN: f32 = PI / 3.0;
pub const BALL_MOVE_ANGLE_MAX: f32 = PI / 3.0 + PI / 20.0;
// 限制区大小
pub const RESTRICTED_AREA_RADIUS_MIN: f32 = 0.8;
pub const RESTRICTED_AREA_RADIUS_MAX: f32 = 1.2;

/// 程序全局量
#[derive(Resource, Copy, Clone, Debug, Default)]
pub struct Statistics {
    pub play_count: usize,
    pub catch_count: usize,
    /// 射门次数
    pub kick_count: usize,
    /// 进球次数
    pub goal_count: usize,
    /// 最高分数
    pub max_score: usize,
    /// 总分数
    pub total_score: usize,
    /// 进程运行次数
    pub process_exec_count: usize,
}

impl AddAssign<&GameStatus> for Statistics {
    fn add_assign(&mut self, rhs: &GameStatus) {
        let GameStatus::End {
            caught, kick, goal, ..
        } = rhs
        else {
            return;
        };
        self.play_count += 1;
        if *caught {
            self.catch_count += 1;
        }
        if *kick {
            self.kick_count += 1;
        }
        if *goal {
            self.goal_count += 1;
        }
        let (this_score, _) = rhs.score();
        self.total_score += this_score;
        self.max_score = usize::max(this_score, self.max_score);
    }
}

impl Add<&GameStatus> for Statistics {
    type Output = Statistics;

    fn add(mut self, rhs: &GameStatus) -> Self::Output {
        self.add_assign(rhs);
        self
    }
}

impl Display for Statistics {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_fmt(format_args!(
            "Play: {} Catch: {} Kick: {} Goal: {}\n",
            self.play_count, self.catch_count, self.kick_count, self.goal_count
        ))?;
        f.write_fmt(format_args!(
            "MaxScore: {} TotalScore: {}",
            self.max_score, self.total_score
        ))
    }
}

/// 程序全局命令
#[derive(Component)]
pub struct GameStatusChanged;

#[derive(Component, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum GameStatus {
    Ready {
        start_time: SystemTime,
    },
    Playing {
        start_time: SystemTime,
        caught: bool,
        kick: bool,
        punish_time: Duration,
    },
    End {
        has_child_process: bool,
        start_time: SystemTime,
        end_time: SystemTime,
        caught: bool,
        kick: bool,
        goal: bool,
        punish_time: Duration,
    },
}

impl GameStatus {
    pub fn score(&self) -> (usize, String) {
        let mut now_score: usize = 0;
        let mut text = String::new();
        let Self::End {
            start_time,
            end_time,
            caught,
            kick,
            goal,
            punish_time,
            ..
        } = self.to_owned()
        else {
            return (now_score, text);
        };
        if caught {
            now_score += 500;
            text += "Caught    +500 \n";
        }
        if kick {
            now_score += 500;
            text += "Kick      +500 \n";
        }
        if goal {
            now_score += 1000;
            text += "Goal      +1000 \n";
        }
        // Time
        let duration = end_time.duration_since(start_time).unwrap_or_default();
        let time_over_limit = f64::max(0.0, duration.as_secs_f64() - 5.0);
        let score_cost_time = (50.0 * time_over_limit) as usize;
        if score_cost_time > 0 {
            now_score = now_score.saturating_sub(score_cost_time);
            text += "Over Time -";
            text += score_cost_time.to_string().as_str();
            text += " \n";
        }
        // Punish
        let score_cost_punish = (2000.0 * punish_time.as_secs_f32()) as usize;
        if score_cost_punish > 0 {
            now_score = now_score.saturating_sub(score_cost_punish);
            text += "Punish    -";
            text += score_cost_punish.to_string().as_str();
            text += " \n";
        }
        text += "\nScore: ";
        text += now_score.to_string().as_str();
        (now_score, text)
    }

    pub fn tips(&self) -> String {
        match self.to_owned() {
            GameStatus::Ready { .. } => {
                "W - Go ahead, S - Go back, A/D - Rotate, Space - Kick.\n\nCatch the ball, then kick!\nPress space to start."
                    .to_string()
            }
            GameStatus::Playing {
                start_time,
                caught,
                kick,
                punish_time,
            } => {
                let now_tips = if kick {
                    "Show Time!"
                } else if caught {
                    "Find the angle, shot!"
                } else {
                    "Catch the ball!"
                };
                let duration_secs = SystemTime::now()
                    .duration_since(start_time)
                    .unwrap_or_default()
                    .as_secs_f64();
                let punish_secs = punish_time.as_secs_f32();
                format!("{}\nElapsed time: {:.3}\nPunish time: {:.3}", now_tips, duration_secs, punish_secs)
            }
            GameStatus::End {
                caught, kick, goal, ..
            } => {
                let result_tips = if goal {
                    "Goal!"
                } else if kick {
                    "Shot!"
                } else if caught {
                    "Taking out."
                } else {
                    "Out."
                };
                let (_, score_tips) = self.score();
                format!("{}\n\n{}\n\nPress Space to restart.", result_tips, score_tips)
            }
        }
    }
}

/// 组件标识
#[derive(Component)]
pub struct TipsText;
#[derive(Component)]
pub struct StatisticsText;

/// 场地信息
#[derive(Copy, Clone, Resource, Debug)]
pub struct FieldData {
    pub field_length: f32,
    pub field_width: f32,
    pub field_color: Color,
    pub line_width: f32,
    pub line_height: f32,
    pub line_color: Color,
    pub penalty_area_length: f32,
    pub penalty_area_width: f32,
    pub goal_area_length: f32,
    pub goal_area_width: f32,
    pub gate_height: f32,
    pub gate_depth: f32,
    pub gate_thickness: f32,
}

impl Default for FieldData {
    fn default() -> Self {
        Self {
            field_length: 18.0,
            field_width: 12.0,
            penalty_area_length: 2.5,
            penalty_area_width: 8.0,
            goal_area_length: 0.5,
            goal_area_width: 5.0,
            field_color: Color::GREEN,
            line_width: 0.2,
            line_height: 0.0001,
            line_color: Color::WHITE,
            gate_height: 2.5,
            gate_depth: 1.5,
            gate_thickness: 0.1,
        }
    }
}

// 球信息
#[derive(Component)]
pub struct Ball;

/// 机器人状态
#[derive(Component, Clone, Copy, Debug)]
pub struct Robot {
    pub last_kick_time: SystemTime,
}

impl Default for Robot {
    fn default() -> Self {
        Self {
            last_kick_time: SystemTime::UNIX_EPOCH,
        }
    }
}

/// 相机追踪地址
#[derive(Component, Clone, Copy, Debug)]
pub struct CameraTrackingTranslation {
    pub translation: Vec3,
    pub rotate_angle: f32,
}

impl CameraTrackingTranslation {
    pub fn get_rotate(self) -> Vec3 {
        Vec3::new(self.rotate_angle.cos(), 0.0, -self.rotate_angle.sin())
    }
}

/// 水平运动组件
#[derive(Component, Clone, Copy, Debug, Default)]
pub struct HorizontalMovingStatus {
    pub rotate_angle: f32,
    pub speed: f32,
    pub locked: bool,
}

impl HorizontalMovingStatus {
    pub fn angle_vec3(&self) -> Vec3 {
        Vec3::new(self.rotate_angle.cos(), 0.0, -self.rotate_angle.sin())
    }

    pub fn delta_distance(&self, time: f32) -> Vec3 {
        if self.locked {
            return Vec3::ZERO;
        }
        self.angle_vec3() * (self.speed * time)
    }
}

/// 子进程
#[derive(Resource, Debug)]
pub struct GameChildProcess {
    pub child: Child,
    pub buffer: Vec<u8>,
    pub ready: bool,
    /// 等待程序返回
    pub waiting: bool,
}

impl GameChildProcess {
    pub fn new(program: &str) -> Result<Self, std::io::Error> {
        Ok(Self {
            child: Command::new(program)
                .stdin(Stdio::piped())
                .stdout(Stdio::piped())
                .spawn()?,
            buffer: Vec::new(),
            ready: false,
            waiting: false,
        })
    }

    pub fn write_data<T: Into<String>>(&mut self, data: T) {
        if self.waiting {
            return;
        }
        let stdin = self.child.stdin.as_mut().unwrap();
        let data_string: String = data.into();
        stdin.write_all(data_string.as_bytes()).unwrap();
        self.waiting = true;
    }

    fn read_buffer(&mut self) {
        let stdout = self.child.stdout.as_mut().unwrap();
        // 将本次读取的数据添加到buffer
        let mut new_buffer = Vec::with_capacity(1000);
        new_buffer.resize(1024, 0xff);
        // info!("Start read, buffer:  {:?}", self.buffer);
        let len_read = stdout.read(new_buffer.as_mut_slice()).unwrap();
        new_buffer.resize(len_read, 0xff);
        // info!("End read, new buffer:{:?}", new_buffer);
        // 将新读取的Buffer添加到末尾
        self.buffer.append(&mut new_buffer);
        // info!(
        //     "End buffer:          {:?}, String:{}",
        //     self.buffer,
        //     String::from_utf8(self.buffer.clone()).unwrap_or_default()
        // );
    }

    pub fn check_ready(&mut self) -> bool {
        self.read_buffer();
        // 确认最后一个字符串是否为OK
        let Ok(new_string) = String::from_utf8(self.buffer.clone()) else {
            return self.ready;
        };
        let split_string = new_string.split_whitespace().filter(|s| !s.is_empty());
        if split_string.last() == Some("OK") {
            // 是的话，确认已准备好
            self.ready = true;
            self.waiting = false;
            // info!("Ready, buffer:{:?}", self.buffer);
            self.buffer.clear();
        }
        self.ready
    }

    #[allow(clippy::result_unit_err)]
    pub fn get_receive_data(&mut self) -> Result<ReceiveData, ()> {
        // 读取Buffer
        self.read_buffer();
        // 从Buffer转换数据
        let new_string = String::from_utf8(self.buffer.clone());
        // info!("String:{:?}", new_string);
        let new_string = new_string.map_err(|_| ())?;
        let data_result = ReceiveData::try_from(new_string.as_str());
        // info!("Recv:  {:?}", data_result);
        let data = data_result.map_err(|_| ())?;
        // 清除缓存和等待状态
        self.buffer.clear();
        self.waiting = false;
        Ok(data)
    }
}

impl Drop for GameChildProcess {
    fn drop(&mut self) {
        if let Err(err) = self.child.kill() {
            warn!("Error when closing child process: {}", err.to_string());
        }
    }
}

/// 限制区
#[derive(Component, Clone, Copy, Debug)]
pub struct RestrictedArea {
    pub x: f32,
    pub z: f32,
    pub radius: f32,
}

#[allow(clippy::from_over_into)]
impl Into<String> for RestrictedArea {
    fn into(self) -> String {
        format!("{} {} {}", self.x, self.z, self.radius,)
    }
}

/// 发送给程序的场地初始化包
#[derive(Clone, Debug)]
pub struct SendInstData {
    pub field_length: f32,
    pub field_width: f32,
    pub goal_area_width: f32,
    pub robot_max_speed: f32,
    pub robot_max_rotate_speed: f32,
    pub robot_radius: f32,
    pub ball_radius: f32,
    pub restrict_area_list: Vec<RestrictedArea>,
}

#[allow(clippy::from_over_into)]
impl Into<String> for SendInstData {
    fn into(self) -> String {
        let mut restricted_area_string = String::new();
        let ra_len = self.restrict_area_list.len();
        for ra in self.restrict_area_list.into_iter() {
            let ra_string: String = ra.into();
            restricted_area_string += ra_string.as_str();
            restricted_area_string += " ";
        }
        format!(
            " {} {} {} {} {} {} {} {} {} ",
            self.field_length,
            self.field_width,
            self.goal_area_width,
            self.robot_max_speed,
            self.robot_max_rotate_speed,
            self.robot_radius,
            self.ball_radius,
            ra_len,
            restricted_area_string,
        )
    }
}

/// 发送给程序的包
#[derive(Clone, Copy, Debug)]
pub struct SendData {
    // Robot
    pub robot_x: f32,
    pub robot_z: f32,
    pub robot_speed: f32,
    pub robot_rotate_angle: f32,
    // Ball
    pub ball_x: f32,
    pub ball_z: f32,
    pub ball_speed: f32,
    pub ball_rotate_angle: f32,
    pub ball_caught: bool,
}

#[allow(clippy::from_over_into)]
impl Into<String> for SendData {
    fn into(self) -> String {
        format!(
            " {} {} {} {} {} {} {} {} {} ",
            self.robot_x,
            self.robot_z,
            self.robot_speed,
            self.robot_rotate_angle,
            self.ball_x,
            self.ball_z,
            self.ball_speed,
            self.ball_rotate_angle,
            self.ball_caught as u8
        )
    }
}

/// 程序返回的包
#[derive(Clone, Copy, Debug)]
pub struct ReceiveData {
    /// 前进速度
    pub robot_speed: f32,
    /// 旋转角速度
    pub robot_rotate_speed: f32,
    /// 是否有射门指令
    pub robot_kick: bool,
}

use core::fmt::Error as FmtError;
use std::ops::{Add, AddAssign};

impl TryFrom<&str> for ReceiveData {
    type Error = FmtError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        let mut values = value.split_whitespace().filter(|s| !s.is_empty());
        // let values: Vec<&str> = value.split_whitespace().filter(|s| !s.is_empty()).collect();
        // info!("TryFrom values: {:?}", values);
        // let mut values = values.into_iter();
        let ret = Self {
            robot_speed: values
                .next()
                .ok_or(FmtError)?
                .parse()
                .map_err(|_| FmtError)?,
            robot_rotate_speed: values
                .next()
                .ok_or(FmtError)?
                .parse()
                .map_err(|_| FmtError)?,
            robot_kick: values
                .next()
                .ok_or(FmtError)?
                .parse::<isize>()
                .map_err(|_| FmtError)?
                != 0,
        };
        // info!("Result: {:?}", ret);
        if values.next().ok_or(FmtError)? != "OK" {
            return Err(FmtError);
        }
        Ok(ret)
    }
}
